Маппинг на основе выражения доступа к свойству

Редактировал(а) Alexandr Fokin 2023/02/15 13:57

public class ExpressionMapper2<TEntity, TValue>
{
   private readonly Dictionary<
       string,
        TValue
        > Mapping;

   private readonly Func<IEnumerable<Expression>, string> KeySelector;
   private static readonly Func<IEnumerable<Expression>, string> DefaultKeySelector =
        (e) =>
        {
           var expression = e.First(e2 => e2 is MemberExpression) as MemberExpression;
           return expression.Member.Name;
        };

   public ExpressionMapper2(
        IEnumerable<(string key, TValue value)> source,
        Func<IEnumerable<Expression>, string>? keySelector = null
        )
    {
        KeySelector = keySelector ?? DefaultKeySelector;
        Mapping = source.ToDictionary(
            e => e.key,
            e => e.value
            );
    }
   public ExpressionMapper2(
        IEnumerable<(Expression<Func<TEntity, object>> keyExpression, TValue value)> source,
        Func<IEnumerable<Expression>, string>? keySelector = null
        )
    {
        KeySelector = keySelector ?? DefaultKeySelector;
        Mapping = source.ToDictionary(
            e => GetKey(e.keyExpression),
            e => e.value
            );
    }


   private string GetKey(Expression expression)
    {
        Visitor visitor = new Visitor();
        visitor.Visit(expression);
       var key = KeySelector(visitor.ExpressionList);
       return key;
    }

   public bool TryGetValue<T>(
        Expression<Func<TEntity, T>> expression,
       out TValue value
        )
    {
       var key = GetKey(expression);
       return Mapping.TryGetValue(
            key,
           out value
            );
    }

   public TValue GetValue<T>(
        Expression<Func<TEntity, T>> expression
        )
    {
       if (TryGetValue(expression, out var value))
        {
           return value;
        }
       else
        {
           throw new KeyNotFoundException();
        }
    }

   // Можно сделать более эффективную и строгую реализацию
   // для примера наиболее простая и общая
   private sealed class Visitor
        : ExpressionVisitor
    {
       public List<Expression> ExpressionList { get; private set; }
            = new List<Expression>();


        [return: NotNullIfNotNull("node")]

       public override Expression? Visit(
            Expression? node
            )
        {
            ExpressionList.Add(node);
           return base.Visit(node);
        }
    }

   public static Builder CreateBuilder()
    {
       return new Builder();
    }

   public class Builder
    {
       private Func<IEnumerable<Expression>, string> KeySelector = DefaultKeySelector;
       private List<(Expression, TValue)> Rules = new List<(Expression, TValue)>();


       public Builder SetKeySelector(
            Func<IEnumerable<Expression>, string> keySelector
            )
        {
            KeySelector = keySelector;
           return this;
        }

       public Builder AddRule<T>(
            Expression<Func<TEntity, T>> rule,
            TValue value
            )
        {
            Rules.Add((rule, value));
           return this;
        }


       public ExpressionMapper2<TEntity, TValue> Build()
        {
            (string key, TValue value)[] rules = new (string key, TValue value)[Rules.Count];
            {
                Visitor visitor = new Visitor();
               int i = 0;
               foreach (var elem in Rules)
                {
                    visitor.Visit(elem.Item1);
                   var key = KeySelector(visitor.ExpressionList);
                    visitor.ExpressionList.Clear();

                    rules[i] = (key, elem.Item2);
                    i++;
                }
            }

           return new ExpressionMapper2<TEntity, TValue>(
                rules,
                KeySelector
                );
        }
    }
}
Теги: